<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="vbIVz" id="vbIVz"><span data-lake-id="u5d7ad736" id="u5d7ad736">典型回答</span></h1>
  <p data-lake-id="u84dc380f" id="u84dc380f"><br></p>
  <p data-lake-id="uf30bb2c5" id="uf30bb2c5"><span data-lake-id="u2979b2d8" id="u2979b2d8">Executors类看起来功能还是比较强大的，又用到了工厂模式、又有比较强的扩展性，重要的是用起来还比较方便，如：</span></p>
  <p data-lake-id="u579ad279" id="u579ad279"><br></p>
  <pre lang="java"><code>
ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;
</code></pre>
  <p data-lake-id="u25b5bc43" id="u25b5bc43"><br></p>
  <p data-lake-id="u69de6275" id="u69de6275"><span data-lake-id="u6564c307" id="u6564c307">即可创建一个固定大小的线程池。</span></p>
  <p data-lake-id="u4e5b0fc3" id="u4e5b0fc3"><br></p>
  <p data-lake-id="u17024264" id="u17024264"><span data-lake-id="ucd123d60" id="ucd123d60">但是为什么在阿里巴巴Java开发手册中也明确指出，不允许使用Executors创建线程池呢</span></p>
  <p data-lake-id="u3528dbbc" id="u3528dbbc"><br></p>
  <p data-lake-id="u5a210145" id="u5a210145"><img src="http://www.hollischuang.com/wp-content/uploads/2018/10/15406254121131.jpg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_67%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u2f7db1df" id="u2f7db1df"><br></p>
  <h3 data-lake-id="8d85cb6a" id="8d85cb6a"><span data-lake-id="u2d05820d" id="u2d05820d">Executors存在什么问题</span></h3>
  <p data-lake-id="u221afc35" id="u221afc35"><br></p>
  <p data-lake-id="u5a5fe2bf" id="u5a5fe2bf"><span data-lake-id="u767a962c" id="u767a962c">在阿里巴巴Java开发手册中提到，使用Executors创建线程池可能会导致OOM(OutOfMemory ,内存溢出)，但是并没有说明为什么，那么接下来我们就来看一下到底为什么不允许使用Executors？</span></p>
  <p data-lake-id="ub31ea3f0" id="ub31ea3f0"><br></p>
  <p data-lake-id="u465f338e" id="u465f338e"><span data-lake-id="uf02c77d0" id="uf02c77d0">我们先来一个简单的例子，模拟一下使用Executors导致OOM的情况。</span></p>
  <p data-lake-id="u0d560493" id="u0d560493"><br></p>
  <pre lang="java"><code>
/**
 * @author Hollis
 */
public class ExecutorsDemo {
    private static ExecutorService executor = Executors.newFixedThreadPool(15);
    public static void main(String[] args) {
        for (int i = 0; i &lt; Integer.MAX_VALUE; i++) {
            executor.execute(new SubThread());
        }
    }
}

class SubThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            //do nothing
        }
    }
}
</code></pre>
  <p data-lake-id="u52777a65" id="u52777a65"><br></p>
  <p data-lake-id="ub3298546" id="ub3298546"><span data-lake-id="udd197ff4" id="udd197ff4">通过指定JVM参数：</span><code data-lake-id="u6fbe50fb" id="u6fbe50fb"><span data-lake-id="ud158d628" id="ud158d628">-Xmx8m -Xms8m</span></code><span data-lake-id="u7cf580fa" id="u7cf580fa"> 运行以上代码，会抛出OOM:</span></p>
  <p data-lake-id="uade962a3" id="uade962a3"><br></p>
  <pre lang="java"><code>
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
</code></pre>
  <p data-lake-id="u7ffa52fb" id="u7ffa52fb"><br></p>
  <p data-lake-id="u80d86ca3" id="u80d86ca3"><span data-lake-id="uf6f23511" id="uf6f23511">以上代码指出，</span><code data-lake-id="uce5e8ce4" id="uce5e8ce4"><span data-lake-id="u8d80802d" id="u8d80802d">ExecutorsDemo.java</span></code><span data-lake-id="u5c208ec9" id="u5c208ec9">的第16行，就是代码中的</span><code data-lake-id="u4cd9c528" id="u4cd9c528"><span data-lake-id="ud94a3489" id="ud94a3489">executor.execute(new SubThread());</span></code><span data-lake-id="ufb17bb53" id="ufb17bb53">。</span></p>
  <p data-lake-id="u980f8162" id="u980f8162"><br></p>
  <h3 data-lake-id="e21332dd" id="e21332dd"><span data-lake-id="u8a83009b" id="u8a83009b">Executors为什么存在缺陷</span></h3>
  <p data-lake-id="ud26e4816" id="ud26e4816"><br></p>
  <p data-lake-id="u9ff8b8b1" id="u9ff8b8b1"><span data-lake-id="u440a8f28" id="u440a8f28">通过上面的例子，我们知道了</span><code data-lake-id="u3e966a8f" id="u3e966a8f"><span data-lake-id="u6e5addfe" id="u6e5addfe">Executors</span></code><span data-lake-id="ud87f2524" id="ud87f2524">创建的线程池存在OOM的风险，那么到底是什么原因导致的呢？我们需要深入</span><code data-lake-id="u700ea769" id="u700ea769"><span data-lake-id="ufde229e6" id="ufde229e6">Executors</span></code><span data-lake-id="u25149fb5" id="u25149fb5">的源码来分析一下。</span></p>
  <p data-lake-id="uddc8ddf7" id="uddc8ddf7"><br></p>
  <p data-lake-id="u072caac4" id="u072caac4"><span data-lake-id="ub126c57f" id="ub126c57f">其实，在上面的报错信息中，我们是可以看出蛛丝马迹的，在以上的代码中其实已经说了，真正的导致OOM的其实是</span><code data-lake-id="u0ae4d9aa" id="u0ae4d9aa"><span data-lake-id="u924abe98" id="u924abe98">LinkedBlockingQueue.offer</span></code><span data-lake-id="u24c076fc" id="u24c076fc">方法。</span></p>
  <p data-lake-id="u0fa2df4b" id="u0fa2df4b"><br></p>
  <pre lang="java"><code>
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
</code></pre>
  <p data-lake-id="u68bf5d1b" id="u68bf5d1b"><br></p>
  <p data-lake-id="u5cd6d037" id="u5cd6d037"><span data-lake-id="u0700c490" id="u0700c490">如果读者翻看代码的话，也可以发现，其实底层确实是通过</span><code data-lake-id="u7e93867b" id="u7e93867b"><span data-lake-id="u166824ee" id="u166824ee">LinkedBlockingQueue</span></code><span data-lake-id="u4bad52c9" id="u4bad52c9">实现的：</span></p>
  <p data-lake-id="uff8b976a" id="uff8b976a"><br></p>
  <pre lang="java"><code>
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue&lt;Runnable&gt;());
</code></pre>
  <p data-lake-id="udce8339a" id="udce8339a"><br></p>
  <p data-lake-id="uf2dce18f" id="uf2dce18f"><span data-lake-id="u54b9369d" id="u54b9369d">如果读者对Java中的阻塞队列有所了解的话，看到这里或许就能够明白原因了。</span></p>
  <p data-lake-id="u5533885c" id="u5533885c"><br></p>
  <p data-lake-id="u9b0a978b" id="u9b0a978b"><span data-lake-id="u46cab185" id="u46cab185">Java中的</span><code data-lake-id="u525d2f8f" id="u525d2f8f"><span data-lake-id="u598202b0" id="u598202b0">BlockingQueue</span></code><span data-lake-id="u8c584d47" id="u8c584d47">主要有两种实现，分别是</span><code data-lake-id="u53df8c06" id="u53df8c06"><span data-lake-id="ud6411379" id="ud6411379">ArrayBlockingQueue</span></code><span data-lake-id="u98e212a0" id="u98e212a0"> 和 </span><code data-lake-id="u3999d67c" id="u3999d67c"><span data-lake-id="uc6999027" id="uc6999027">LinkedBlockingQueue</span></code><span data-lake-id="u347aa1dc" id="u347aa1dc">。</span></p>
  <p data-lake-id="u8abad37f" id="u8abad37f"><br></p>
  <p data-lake-id="u4d3ea3fd" id="u4d3ea3fd"><code data-lake-id="u51349613" id="u51349613"><span data-lake-id="u0c62ef0c" id="u0c62ef0c">ArrayBlockingQueue</span></code><span data-lake-id="u6f2e2224" id="u6f2e2224">是一个用数组实现的有界阻塞队列，必须设置容量。</span></p>
  <p data-lake-id="u7858d4ca" id="u7858d4ca"><br></p>
  <p data-lake-id="udabb800a" id="udabb800a"><code data-lake-id="u7246a356" id="u7246a356"><span data-lake-id="u29c73e7d" id="u29c73e7d">LinkedBlockingQueue</span></code><span data-lake-id="u7bf2b7a7" id="u7bf2b7a7">是一个用链表实现的有界阻塞队列，容量可以选择进行设置，不设置的话，将是一个无边界的阻塞队列，最大长度为</span><code data-lake-id="u584bc30d" id="u584bc30d"><span data-lake-id="u90e38bb3" id="u90e38bb3">Integer.MAX_VALUE</span></code><span data-lake-id="u5905b886" id="u5905b886">。</span></p>
  <p data-lake-id="ude24cdf4" id="ude24cdf4"><br></p>
  <p data-lake-id="u388d0a38" id="u388d0a38"><span data-lake-id="u7487f9b4" id="u7487f9b4">这里的问题就出在：</span><strong><span data-lake-id="u6f534ac6" id="u6f534ac6">不设置的话，将是一个无边界的阻塞队列，最大长度为Integer.MAX_VALUE。</span></strong><span data-lake-id="u2eb3217e" id="u2eb3217e">也就是说，如果我们不设置</span><code data-lake-id="u465b219e" id="u465b219e"><span data-lake-id="ubc411163" id="ubc411163">LinkedBlockingQueue</span></code><span data-lake-id="ud7d57016" id="ud7d57016">的容量的话，其默认容量将会是</span><code data-lake-id="ufd1eda6a" id="ufd1eda6a"><span data-lake-id="ua1b1cbc5" id="ua1b1cbc5">Integer.MAX_VALUE</span></code><span data-lake-id="u691c78ae" id="u691c78ae">。</span></p>
  <p data-lake-id="uce246e22" id="uce246e22"><br></p>
  <p data-lake-id="u321d23b1" id="u321d23b1"><span data-lake-id="uf21be4c9" id="uf21be4c9">而</span><code data-lake-id="u1eb6d955" id="u1eb6d955"><span data-lake-id="u14706a75" id="u14706a75">newFixedThreadPool</span></code><span data-lake-id="ud4472f0d" id="ud4472f0d">中创建</span><code data-lake-id="uc7c1f803" id="uc7c1f803"><span data-lake-id="u0ca51e33" id="u0ca51e33">LinkedBlockingQueue</span></code><span data-lake-id="u4004021c" id="u4004021c">时，并未指定容量。此时，</span><code data-lake-id="ued230e4d" id="ued230e4d"><span data-lake-id="u4021a7dd" id="u4021a7dd">LinkedBlockingQueue</span></code><span data-lake-id="u46ded3aa" id="u46ded3aa">就是一个无边界队列，对于一个无边界队列来说，是可以不断的向队列中加入任务的，这种情况下就有可能因为任务过多而导致内存溢出问题。</span></p>
  <p data-lake-id="u34a2245b" id="u34a2245b"><br></p>
  <p data-lake-id="ub04411d9" id="ub04411d9"><span data-lake-id="ue9d91a8b" id="ue9d91a8b">上面提到的问题主要体现在</span><code data-lake-id="u2a1c8dba" id="u2a1c8dba"><span data-lake-id="ub1c8c479" id="ub1c8c479">newFixedThreadPool</span></code><span data-lake-id="ubf06ef64" id="ubf06ef64">和</span><code data-lake-id="u53aa3abf" id="u53aa3abf"><span data-lake-id="u244f1e37" id="u244f1e37">newSingleThreadExecutor</span></code><span data-lake-id="u0560ce14" id="u0560ce14">两个工厂方法上，并不是说</span><code data-lake-id="udba89e78" id="udba89e78"><span data-lake-id="ub27a4303" id="ub27a4303">newCachedThreadPool</span></code><span data-lake-id="ubbf9fe8e" id="ubbf9fe8e">和</span><code data-lake-id="ub6fe46fe" id="ub6fe46fe"><span data-lake-id="u07ee79ab" id="u07ee79ab">newScheduledThreadPool</span></code><span data-lake-id="u4f7ab7ea" id="u4f7ab7ea">这两个方法就安全了，这两种方式创建的最大线程数可能是</span><code data-lake-id="u3f95f856" id="u3f95f856"><span data-lake-id="u0a9be355" id="u0a9be355">Integer.MAX_VALUE</span></code><span data-lake-id="ub9754921" id="ub9754921">，而创建这么多线程，必然就有可能导致OOM。</span></p>
  <p data-lake-id="u709879a6" id="u709879a6"><span data-lake-id="uc09eadcf" id="uc09eadcf">​</span><br></p>
  <h1 data-lake-id="Bvdm4" id="Bvdm4"><span data-lake-id="u83b7c16e" id="u83b7c16e">扩展知识</span></h1>
  <h2 data-lake-id="qimFw" id="qimFw"><span data-lake-id="u2c527dae" id="u2c527dae">如何正确创建线程池</span></h2>
  <p data-lake-id="u5c44b954" id="u5c44b954"><br></p>
  <p data-lake-id="ud14d57ab" id="ud14d57ab"><span data-lake-id="u434accdd" id="u434accdd">避免使用Executors创建线程池，主要是避免使用其中的默认实现，那么我们可以自己直接调用`ThreadPoolExecutor`的构造函数来自己创建线程池。在创建的同时，给`BlockQueue`指定容量就可以了。</span></p>
  <p data-lake-id="u214d2339" id="u214d2339"><span data-lake-id="u968936d1" id="u968936d1">​</span><br></p>
  <pre lang="java"><code>
private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));
</code></pre>
  <p data-lake-id="u37141af5" id="u37141af5"><br></p>
  <p data-lake-id="uf94c0220" id="uf94c0220"><span data-lake-id="u5037d84b" id="u5037d84b">这种情况下，一旦提交的线程数超过当前可用线程数时，就会抛`java.util.concurrent.RejectedExecutionException，这是因为当前线程池使用的队列是有边界队列，队列已经满了便无法继续处理新的请求。但是异常（Exception）总比发生错误（Error）要好。</span></p>
  <p data-lake-id="u703b7c5e" id="u703b7c5e"><span data-lake-id="u6038b397" id="u6038b397">​</span><br></p>
  <p data-lake-id="u40b5f2b5" id="u40b5f2b5"><span data-lake-id="u126e5a19" id="u126e5a19">除了自己定义`ThreadPoolExecutor`外。还有其他方法。这个时候第一时间就应该想到开源类库，如apache和guava等。</span></p>
  <p data-lake-id="ub7f7fbbf" id="ub7f7fbbf"><span data-lake-id="ua9e6436d" id="ua9e6436d">​</span><br></p>
  <p data-lake-id="ue9f984bd" id="ue9f984bd"><span data-lake-id="u51ae0ccb" id="u51ae0ccb">作者推荐使用guava提供的ThreadFactoryBuilder来创建线程池。</span></p>
  <p data-lake-id="uebd217c1" id="uebd217c1"><span data-lake-id="ubac061f7" id="ubac061f7">​</span><br></p>
  <pre lang="java"><code>
public class ExecutorsDemo {

    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();

    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue&lt;Runnable&gt;(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {

        for (int i = 0; i &lt; Integer.MAX_VALUE; i++) {
            pool.execute(new SubThread());
        }
    }
}
</code></pre>
  <p data-lake-id="u5439f0bb" id="u5439f0bb"><span data-lake-id="ud037f429" id="ud037f429">​</span><br></p>
  <p data-lake-id="u66e0b312" id="u66e0b312"><span data-lake-id="uf4d16cb7" id="uf4d16cb7">​</span><br></p>
  <p data-lake-id="u4dabacdb" id="u4dabacdb"><span data-lake-id="u4026e8a9" id="u4026e8a9">通过上述方式创建线程时，不仅可以避免OOM的问题，还可以自定义线程名称，更加方便的出错的时候溯源。</span></p>
  <p data-lake-id="ub21131be" id="ub21131be"><span data-lake-id="ufbad5c21" id="ufbad5c21">​</span><br></p>
 </body>
</html>